#!/bin/sh

# RedPorts tinderbox backend
#
# $Id: rptinderbox 575 2013-06-27 06:14:00Z decke@bluelife.at $
#
# /tmp/rptinderbox/.lock              Global lock for maintenance
# /tmp/rptinderbox/<build>/.lock      Build lock including shellscript variables
# /tmp/rptinderbox/<build>/.checkout  Checkout done
# /tmp/rptinderbox/<build>/.build     Build started
# /tmp/rptinderbox/<build>/.finished  Build finished
# /tmp/rptinderbox/<build>/ports/     Portstree overlay
#
# /usr/local/tinderbox/portstrees/<portstree>/ports/   Tinderbox portstree
# 

RPTB_VERSION="1.0.0"

ZFSROOT="zroot/tinderbox/%s/portstree"
ZFSREPOROOT="zroot/tinderbox"

WRKBASE=/tmp/rptinderbox
TINDERBOX=/usr/local/tinderbox
CLEANROOM_MODE=yes

checkout()
{
    TYPE=$1
    REPOSITORY=$2
    REVISION=$3
    BUILD=$4

    if [ -z "${BUILD}" ]; then
        echo "Build not specified."
        return 1
    fi

    if [ -f "${WRKBASE}/.lock" ]; then
        echo "Maintenance lock exists."
        return 1
    fi

    if [ -f "${TINDERBOX}/builds/${BUILD}/tinderdlock" ]; then
        echo "Tinderbox lock ${TINDERBOX}/builds/${BUILD}/tinderdlock exists."
        return 1
    fi

    LOCK=${WRKBASE}/${BUILD}/.lock

    if [ -f "${LOCK}" ]; then
        echo "Lock ${LOCK} exists."
        return 1
    fi

    if [ ! -d "${WRKBASE}/${BUILD}" ]; then
        mkdir -p ${WRKBASE}/${BUILD} || return 1
    fi

    # create build lock
    touch ${LOCK} || return 1

    PORTSTREE=`${TINDERBOX}/scripts/tc getPortsTreeForBuild -b ${BUILD} 2>/dev/null`

    if [ -z "${PORTSTREE}" ]; then
        echo "Unknown portstree for build ${BUILD}"
        return 1
    fi

    if [ "${TYPE}" != "svn" -a "${TYPE}" != "svn-full" ]; then
        echo "Unknown repository type"
        return 1
    fi

    # populate lock
    echo "TYPE=\"${TYPE}\"" >> ${LOCK}
    echo "REPOSITORY=\"${REPOSITORY}\"" >> ${LOCK}
    echo "BUILD=\"${BUILD}\"" >> ${LOCK}
    echo "PORTSTREE=\"${PORTSTREE}\"" >> ${LOCK}
    echo "LOCK=\"${LOCK}\"" >> ${LOCK}

    # checkout overlay ports
    PARAMS="--non-interactive"
    if [ ! -z "${REVISION}" ]; then
        PARAMS="${PARAMS} --revision ${REVISION}"
    fi

    if [ "${TYPE}" = "svn" ]; then
        ZFSVOLUME=`printf "${ZFSROOT}" ${PORTSTREE}`
        echo "ZFSVOLUME=\"${ZFSVOLUME}@work\"" >> ${LOCK}

        svn checkout ${PARAMS} ${REPOSITORY} ${WRKBASE}/${BUILD}/ports >/dev/null || return 2
        zfs snapshot ${ZFSVOLUME}@work || return 3

        # apply overlay ports
        find ${WRKBASE}/${BUILD}/ports -type d -d 2 | grep -v "${WRKBASE}/${BUILDS}/ports/Mk/" | sed -e "s|^${WRKBASE}/${BUILD}/ports/|${TINDERBOX}/portstrees/${PORTSTREE}/ports/|" | xargs rm -Rf || return 1
        cp -pr ${WRKBASE}/${BUILD}/ports/* ${TINDERBOX}/portstrees/${PORTSTREE}/ports/
    elif [ "${TYPE}" = "svn-full" ]; then
        ZFSVOLUME=`printf "${ZFSROOT}" ${PORTSTREE}`
        echo "ZFSVOLUME=\"${ZFSVOLUME}@work\"" >> ${LOCK}

        zfs snapshot ${ZFSVOLUME}@work || return 3
        ln -s ${TINDERBOX}/portstrees/${PORTSTREE}/ports ${WRKBASE}/${BUILD}/ports || return 3

        BASEREPO=`svn info ${TINDERBOX}/portstrees/${PORTSTREE}/ports | grep "URL:" | cut -d' ' -f2-`

        CURREVISION=`svn info --xml ${TINDERBOX}/portstrees/${PORTSTREE}/ports | xml sel -t -m "/info/entry" -v "@revision"`

        PORTS=`svn diff -r ${CURREVISION}:${REVISION} --summarize --xml ${TINDERBOX}/portstrees/${PORTSTREE}/ports | xml sel -t -m "/diff/paths/path" -v "node()" -n | cut -d'/' -f8-9 | sort | uniq | grep -v '^$' | xargs`

        cd ${TINDERBOX}/portstrees/${PORTSTREE}/ports && svn update ${PARAMS} ${PORTS} >/dev/null || return 2
    fi

    if [ -z "${REVISION}" ]; then
        REVISION=`svn info ${WRKBASE}/${BUILD}/ports | grep "Revision:" | cut -d: -f2`
        REVISION=`expr ${REVISION} + 0`
        echo "REVISION=${REVISION}"
    fi

    echo "REVISION=\"${REVISION}\"" >> ${LOCK}
  
    # checkout finished
    touch ${WRKBASE}/${BUILD}/.checkout || return 1
  
    return 0
}

clean()
{
    BUILD=$1
    LOCK=${WRKBASE}/${BUILD}/.lock

    if [ ! -d "${WRKBASE}/${BUILD}" ]; then
        return 0
    fi

    if [ ! -f "${LOCK}" ]; then
        echo "No lock for build ${BUILD}"
        return 1
    fi

    . ${LOCK}

    if [ -f "${WRKBASE}/${BUILD}/.build" ]; then
        if [ ! -f "${WRKBASE}/${BUILD}/.finished" ]; then
            ${TINDERBOX}/scripts/tc rmBuildPortsQueueEntry -b ${BUILD} -d ${PORT}
        fi
    fi

    if [ ! -z "${ZFSVOLUME}" ]; then
        zfs rollback ${ZFSVOLUME}
        zfs destroy -R ${ZFSVOLUME}
    fi

    if [ -f "${WRKBASE}/${BUILD}/.build" -a ! -z "${LASTPACKAGEDATE}" ]; then
        NEWPACKAGES=`cd ${TINDERBOX}/packages/${PORTSTREE}/All/ && find * -type f -newermt "${LASTPACKAGEDATE}" -print`

        for package in ${NEWPACKAGES}
        do
            if [ -z "${package#*.txz}" ]; then
                PKGPORT=`pkg info -oqF ${TINDERBOX}/packages/${PORTSTREE}/All/${package}`
            else
                PKGPORT=`pkg_info -oq ${TINDERBOX}/packages/${PORTSTREE}/All/${package}`
            fi
 
            if [ ! -z "${CLEANROOM_MODE}" ]; then
                if [ -d "${WRKBASE}/${BUILD}/ports/${PKGPORT}" ]; then
                    find -L ${TINDERBOX}/packages/${PORTSTREE} -samefile ${TINDERBOX}/packages/${PORTSTREE}/All/${package} | xargs rm
                fi
            else
                if [ "${PKGPORT}" = "${PORT}" ]; then
                    find -L ${TINDERBOX}/packages/${PORTSTREE} -samefile ${TINDERBOX}/packages/${PORTSTREE}/All/${package} | xargs rm
                fi
            fi
        done
    fi

    # clean buildqueue from failed builds
    for ENTRY in `${TINDERBOX}/scripts/tc listBuildPortsQueue -s FAIL -r | egrep ":${BUILD}:"`
    do
        ID=`echo ${ENTRY} | cut -d: -f1`

        ${TINDERBOX}/scripts/tc rmBuildPortsQueueEntry -i ${ID}
    done

    # clean buildqueue from succeeded builds
    for ENTRY in `${TINDERBOX}/scripts/tc listBuildPortsQueue -s SUCCESS -r | egrep ":${BUILD}:"`
    do
        ID=`echo ${ENTRY} | cut -d: -f1`

        ${TINDERBOX}/scripts/tc rmBuildPortsQueueEntry -i ${ID}
    done

    if ${TINDERBOX}/scripts/tc listBuildPortsQueue -r | egrep ":${BUILD}:" >/dev/null ; then
        echo "Still queued builds for ${BUILD}"
        return 1
    fi

    rm -rf ${WRKBASE}/${BUILD} || return 1

    return 0
}

recover()
{
    BUILD=$1
    DBUSER=`cat ${TINDERBOX}/scripts/ds.ph | grep "DB_USER" | cut -d"'" -f2`
    DBPASS=`cat ${TINDERBOX}/scripts/ds.ph | grep "DB_PASS" | cut -d"'" -f2`
    DBHOST=`cat ${TINDERBOX}/scripts/ds.ph | grep "DB_HOST" | cut -d"'" -f2`
    DBNAME=`cat ${TINDERBOX}/scripts/ds.ph | grep "DB_NAME" | cut -d"'" -f2`

    if [ ! -z "${DBPASS}" ]; then
        DBPASS="-p${DBPASS}"
    fi

    # reset build status in database to IDLE
    mysql -u${DBUSER} ${DBPASS} -h ${DBHOST} -e "UPDATE builds SET build_status = \"IDLE\" WHERE build_name = \"${BUILD}\";" ${DBNAME} 2>/dev/null || return 1

    # cleanup tinderbox locks
    rm -f ${TINDERBOX}/builds/${BUILD}/lock
    rm -f ${TINDERBOX}/builds/${BUILD}/tinderdlock


    # cleanup tinderbox queue
    for ENTRY in `${TINDERBOX}/scripts/tc listBuildPortsQueue -r | egrep ":${BUILD}:"`
    do
        QUEUEID=`echo ${ENTRY} | cut -d: -f1`
        PORT=`echo ${ENTRY} | cut -d: -f4`

        ${TINDERBOX}/scripts/tc rmBuildPortsQueueEntry -i ${QUEUEID}
    done

    # remove zfs snapshot
    ZFSVOLUME=`printf "${ZFSROOT}" ${BUILD}`
    if zfs list -t snapshot | grep "${ZFSVOLUME}" >/dev/null ; then
        zfs destroy ${ZFSVOLUME}@work 2>/dev/null || return 1
    fi

    # unmount tmpfs
    #while umount -f ${ZFSROOT}/tinderbox/${BUILD} 2>/dev/null ; do
    #    sleep 1
    #done

    # remove 
    rm -rf ${WRKBASE}/${BUILD} || return 1

    return 0
}

build()
{
    PORT=$1
    BUILD=$2
    PRIORITY=$3
    FINISHURL=$4
    LOCK=${WRKBASE}/${BUILD}/.lock

    if [ -f "${WRKBASE}/.lock" ]; then
        echo "Maintenance lock exists."
        return 1
    fi

    if [ ! -f "${LOCK}" ]; then
        echo "No lock for build ${BUILD}"
        return 1
    fi

    . ${LOCK}

    if [ ! -f "${WRKBASE}/${BUILD}/.checkout" ]; then
        echo "No checkout for build ${BUILD}"
        return 1
    fi

    if [ -f "${WRKBASE}/${BUILD}/.build" ]; then
        echo "Build already running"
        return 1
    fi

    if [ -f "${WRKBASE}/${BUILD}/.finished" ]; then
        echo "Build already finished"
        return 1
    fi

    PORTSTREE=`${TINDERBOX}/scripts/tc getPortsTreeForBuild -b ${BUILD} 2>/dev/null`

    if [ -z "${PORTSTREE}" ]; then
        echo "Unknown portstree for build ${BUILD}"
        return 1
    fi

    # for svn-full repositories we cannot delete the package cache
    if [ -L "${WRKBASE}/${BUILD}/ports" ]; then
        LASTPACKAGE=""
        LASTPACKAGEDATE=""
    # determine most recent package in the cache
    elif [ -d "${TINDERBOX}/packages/${BUILD}/All" ]; then
        LASTPACKAGE=`ls -tr ${TINDERBOX}/packages/${BUILD}/All | grep -v "Makefile" | tail -1`
        LASTPACKAGEDATE=`stat -f %m ${TINDERBOX}/packages/${BUILD}/All/${LASTPACKAGE}`
        LASTPACKAGEDATE=`date -r ${LASTPACKAGEDATE} "+%Y-%m-%d %H:%M:%S"`
    else
        LASTPACKAGE=""
        LASTPACKAGEDATE=`date "+%Y-%m-%d %H:%M:%S"`
    fi

    echo "PORT=\"${PORT}\"" >> ${LOCK}
    echo "PRIORITY=\"${PRIORITY}\"" >> ${LOCK}
    echo "FINISHURL=\"${FINISHURL}\"" >> ${LOCK}
    echo "LASTPACKAGE=\"${LASTPACKAGE}\"" >> ${LOCK}
    echo "LASTPACKAGEDATE=\"${LASTPACKAGEDATE}\"" >> ${LOCK}

    if [ -f "${TINDERBOX}/portstrees/${PORTSTREE}/ports/${PORT}/Makefile" ]; then
        if cd ${TINDERBOX}/portstrees/${PORTSTREE}/ports/${PORT} && make -V PKGVERSION >/dev/null 2>/dev/null ; then
            PKGVERSION=`cd ${TINDERBOX}/portstrees/${PORTSTREE}/ports/${PORT} && make -V PKGVERSION`
            echo "PKGVERSION=\"${PKGVERSION}\"" >> ${LOCK}
            echo "PKGVERSION=${PKGVERSION}"
        fi

        ${TINDERBOX}/scripts/tc addBuildPortsQueueEntry -b ${BUILD} -d ${PORT} -p ${PRIORITY} 2>/dev/null || return 1
    else
        touch ${WRKBASE}/${BUILD}/.finished
        echo "BUILDSTATUS=\"FAIL\"" >> ${WRKBASE}/${BUILD}/.finished
        echo "FAIL_REASON=\"Port does not exist\"" >> ${WRKBASE}/${BUILD}/.finished
    fi

    touch ${WRKBASE}/${BUILD}/.build || return 1

    return 0
}


status()
{
    BUILD=$1
    STATUS="unknown"
    BUILDSTATUS=""

    if ${TINDERBOX}/scripts/tc listBuildPortsQueue -s FAIL -r | egrep ":${BUILD}:" >/dev/null ; then
        echo "STATUS=finished"
        echo "BUILDSTATUS=FAIL"
        return 0
    fi

    if ${TINDERBOX}/scripts/tc listBuildPortsQueue -s ENQUEUED -r | egrep ":${BUILD}:" >/dev/null ; then
        STATUS="building"
    elif ${TINDERBOX}/scripts/tc listBuildPortsQueue -s PROCESSING -r | egrep ":${BUILD}:" >/dev/null ; then
        STATUS="building"
    elif ${TINDERBOX}/scripts/tc listBuildPortsQueue -s SUCCESS -r | egrep ":${BUILD}:" >/dev/null ; then
        STATUS="finished"
    elif [ -f "${WRKBASE}/${BUILD}/.lock" ]; then
        STATUS="busy"
    else
        STATUS="idle"

        # Portstree last builtdate
        PORTSTREE=`${TINDERBOX}/scripts/tc getPortsTreeForBuild -b ${BUILD} 2>/dev/null`

        if [ -z "${PORTSTREE}" ]; then
            echo "Unknown portstree for build ${BUILD}"
            return 1
        fi

        PORTSTREELASTBUILT=`${TINDERBOX}/scripts/tc dumpObject -t ${PORTSTREE} | grep ports_tree_last_built | cut -d":" -f2- | cut -d" " -f2- 2>/dev/null`

        echo "PORTSTREELASTBUILT=${PORTSTREELASTBUILT}"
    fi

    if [ -f "${WRKBASE}/${BUILD}/.finished" ]; then
        . ${WRKBASE}/${BUILD}/.finished

        if [ -n "${FAIL_REASON}" ]; then
            echo "FAIL_REASON=${FAIL_REASON}"
        fi

        if [ -n "${BUILDLOG}" ]; then
            echo "BUILDLOG=${BUILDLOG}"
        fi

        if [ -n "${WRKDIR}" ]; then
            echo "WRKDIR=${WRKDIR}"
        fi
    fi

    echo "STATUS=${STATUS}"

    if [ -n "${BUILDSTATUS}" ]; then
        echo "BUILDSTATUS=${BUILDSTATUS}"
    fi

    return 0
}

update()
{
    BUILD=$1
    LOCK=${WRKBASE}/${BUILD}/.lock

    if [ -f "${WRKBASE}/.lock" ]; then
        echo "Maintenance lock exists."
        return 1
    fi

    if [ -f "${LOCK}" ]; then
        echo "Lock for build ${BUILD} exists."
        return 1
    fi

    if [ ! -d "${WRKBASE}/${BUILD}" ]; then
        mkdir -p ${WRKBASE}/${BUILD} || return 1
    fi

    # create build lock
    touch ${LOCK} || return 1

    # determine portstree
    PORTSTREE=`${TINDERBOX}/scripts/tc getPortsTreeForBuild -b ${BUILD} 2>/dev/null`

    if [ -z "${PORTSTREE}" ]; then
        echo "Unknown portstree for build ${BUILD}"
        rm -f ${LOCK}
        return 1
    fi

    # update portstree
    if ! ${TINDERBOX}/scripts/tc updatePortsTree -p ${PORTSTREE} >/dev/null ; then
        mv ${TINDERBOX}/portstrees/${PORTSTREE}/update.log ${TINDERBOX}/portstrees/${PORTSTREE}/update.log.`date "+%Y%m%d%H%M"`
        rm -rf ${TINDERBOX}/portstrees/${PORTSTREE}/ports
        rm -f ${LOCK}
        return 1
    fi

    rm -f ${LOCK}
    rmdir ${WRKBASE}/${BUILD}
    return 0
}

selftest()
{
    BUILD=$1

    if [ ! -d "${TINDERBOX}/builds/${BUILD}" ]; then
        echo "Unknown build ${BUILD}"
        return 1
    fi

    PORTSTREE=`${TINDERBOX}/scripts/tc getPortsTreeForBuild -b ${BUILD} 2>/dev/null`

    if [ -z "${PORTSTREE}" ]; then
        echo "Unknown portstree for build ${BUILD}"
        return 1
    fi

    ZFSVOLUME=`printf "${ZFSROOT}" ${PORTSTREE}`
    if ! zfs list ${ZFSVOLUME} >/dev/null ; then
        echo "ZFS volume ${ZFSVOLUME} does not exist"
        return 1
    fi

    return 0
}

case "$1" in
'build')
    if build "$2" "$3" "$4" "$5" 2>&1 ; then
        echo "OK"
        return 0
    else
        echo "ERROR"
        return 1
    fi
;;
'checkout')
    if checkout "$2" "$3" "$4" "$5" 2>&1 ; then
        echo "OK"
        return 0
    else
        echo "ERROR"
        return 1
    fi
;;
'clean')
    if clean "$2" 2>&1 ; then
        echo "OK"
        return 0
    else
        echo "ERROR"
        return 1
    fi
    return $?
;;
'recover')
    if recover "$2" 2>&1 ; then
        echo "OK"
        return 0
    else
        echo "ERROR"
        return 1
    fi
    return $?
;;
'status')
    if status "$2" 2>&1 ; then
        echo "OK"
        return 0
    else
        echo "ERROR"
        return 1
    fi
;;
'update')
    if update "$2" 2>&1 ; then
        echo "OK"
        return 0
    else
        echo "ERROR"
        return 1
    fi
;;
'selftest')
    if selftest "$2" 2>&1 ; then
        echo "OK"
        return 0
    else
        echo "ERROR"
        return 1
    fi
;;
*)
    echo "Usage: $0 [build|checkout|clean|status|update|selftest] param"
    exit 1
;;
esac

exit 0
